#include <strmif.h>
#include "mkvcontext.hpp"
#include "mkvstreamaudiovorbis.hpp"
#include "vorbistypes.hpp"
#include "cmediatypes.hpp"
#include <cassert>
#include <uuids.h>
#if 0 //def _DEBUG
#include <odbgstream.hpp>
#include <iomanip>
using std::endl;
using std::hex;
using std::dec;
using std::setfill;
using std::setw;
#endif

namespace MkvMux
{


void MkvStreamAudioVorbis::GetMediaTypes(CMediaTypes& mtv)
{
    AM_MEDIA_TYPE mt;
    
    mt.majortype = MEDIATYPE_Audio;
    mt.subtype = VorbisTypes::MEDIASUBTYPE_VORBIS2_AUDIO;    
    mt.bFixedSizeSamples = FALSE;
    mt.bTemporalCompression = FALSE;
    mt.lSampleSize = 0;
    mt.formattype = GUID_NULL;
    mt.pUnk = 0;
    mt.cbFormat = 0;
    mt.pbFormat = 0;
    
    mtv.Add(mt);
}


bool MkvStreamAudioVorbis::QueryAccept(const AM_MEDIA_TYPE& mt)
{
    if (mt.majortype != MEDIATYPE_Audio)
        return false;
        
    if (mt.subtype != VorbisTypes::MEDIASUBTYPE_VORBIS2_AUDIO)
        return false;
        
    if (mt.formattype != VorbisTypes::FORMAT_VorbisFormat2)
        return false;
    
    using VorbisTypes::VORBISFORMAT2;
        
    if (mt.cbFormat < sizeof(VORBISFORMAT2))
        return false;
        
    if (mt.pbFormat == 0)
        return false;
        
    const VORBISFORMAT2& fmt = (VORBISFORMAT2&)(*mt.pbFormat);
    
    if (fmt.dwChannels == 0)
        return false;
        
    if (fmt.dwChannels > 255)
        return false;
        
    if (fmt.dwSamplesPerSec == 0)
        return false;
        
    //TODO: fmt.dwBitsPerSample
    
    const DWORD ident_len = fmt.dwHeaderSizes[0];
    
    if (ident_len == 0)  //TODO: should be 30?
        return false;    
        
    const DWORD comments_len = fmt.dwHeaderSizes[1];
    
    if (comments_len == 0)
        return false;
        
    const DWORD setup_len = fmt.dwHeaderSizes[2];
    
    if (setup_len == 0)
        return false;
        
    const DWORD hdr_len = ident_len + comments_len + setup_len;
    const DWORD cbFormat = sizeof(VORBISFORMAT2) + hdr_len;
    
    if (mt.cbFormat < cbFormat)
        return false;
        
    return true;
}


HRESULT MkvStreamAudioVorbis::GetAllocatorRequirements(
    const AM_MEDIA_TYPE&,
    ALLOCATOR_PROPERTIES& props)
{
    //Here we assume a Vorbis audio packet is 20ms in duration,
    //which makes 50 packets per one second of audio.  We then buffer
    //3 seconds worth of audio.
    //TODO: determine what the duration of a Vorbis packet actually is.

    props.cBuffers = 3 * 50;
    props.cbBuffer = 0;
    props.cbAlign = 0;
    props.cbPrefix = 0;
    
    return S_OK;
}


MkvStreamAudio* MkvStreamAudioVorbis::CreateStream(
    MkvContext& ctx,
    const AM_MEDIA_TYPE& mt)
{
    assert(QueryAccept(mt));
    
    const BYTE* const pb = mt.pbFormat;
    const ULONG cb = mt.cbFormat;
    
    return new (std::nothrow) MkvStreamAudioVorbis(ctx, pb, cb);
}



MkvStreamAudioVorbis::VorbisFrame::VorbisFrame(IMediaSample* p) :
    m_pSample(p)
{
    m_pSample->AddRef();
}


MkvStreamAudioVorbis::VorbisFrame::~VorbisFrame()
{
    const ULONG n = m_pSample->Release();
    n;
}


const VorbisTypes::VORBISFORMAT2& 
MkvStreamAudioVorbis::GetFormat() const
{
    using VorbisTypes::VORBISFORMAT2;
    
    ULONG cb;
    const void* const pv = MkvStreamAudio::GetFormat(cb);
    assert(pv);
    assert(cb >= sizeof(VORBISFORMAT2));
    
    const VORBISFORMAT2* const pfmt = static_cast<const VORBISFORMAT2*>(pv);
    assert(pfmt);
    
    const VORBISFORMAT2& fmt = *pfmt;
    return fmt;
}


ULONG MkvStreamAudioVorbis::GetSamplesPerSec() const
{
    const VorbisTypes::VORBISFORMAT2& fmt = GetFormat();
    assert(fmt.dwSamplesPerSec > 0);

    return fmt.dwSamplesPerSec;
}


BYTE MkvStreamAudioVorbis::GetChannels() const
{
    const VorbisTypes::VORBISFORMAT2& fmt = GetFormat();
    assert(fmt.dwChannels > 0);
    assert(fmt.dwChannels <= 255);

    return static_cast<BYTE>(fmt.dwChannels);
}



ULONG MkvStreamAudioVorbis::VorbisFrame::curr_timecode_ms() const
{
    __int64 st, sp;

    const HRESULT hr = m_pSample->GetTime(&st, &sp);
    assert(SUCCEEDED(hr));    
    assert(st >= 0);    
    //assert((st % 10000) == 0);
    
    const __int64 ms = st / 10000;
    const ULONG result = static_cast<ULONG>(ms);
    
    return result;
}


ULONG MkvStreamAudioVorbis::VorbisFrame::size() const
{
   const long result = m_pSample->GetActualDataLength();
   assert(result >= 0);
   
   return result;
}


const BYTE* MkvStreamAudioVorbis::VorbisFrame::data() const
{
    BYTE* ptr;
    
    const HRESULT hr = m_pSample->GetPointer(&ptr);
    assert(SUCCEEDED(hr));
    assert(ptr);
    
    return ptr;
}


MkvStreamAudioVorbis::MkvStreamAudioVorbis(
    MkvContext& ctx,
    const BYTE* pb,
    ULONG cb) :
    MkvStreamAudio(ctx, pb, cb)
{
}


void MkvStreamAudioVorbis::WriteTrackCodecID()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID1(0x86);  //Codec ID
    f.Write1String("A_VORBIS");
}


void MkvStreamAudioVorbis::WriteTrackCodecName()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID3(0x258688);  //Codec Name
    f.Write1UTF8(L"VORBIS");
}


void MkvStreamAudioVorbis::WriteTrackCodecPrivate()
{
    typedef VorbisTypes::VORBISFORMAT2 F;
    
    ULONG cb;
    const void* const pv = MkvStreamAudio::GetFormat(cb);
    assert(cb >= sizeof(F));
    assert(pv);
    
    const F* const pf = static_cast<const F*>(pv);
    const F& f = *pf;
    assert(f.dwChannels > 0);
    assert(f.dwSamplesPerSec > 0);
    
    const DWORD ident_len = f.dwHeaderSizes[0];
    assert(ident_len > 0);
    assert(ident_len <= 255);
    
    const DWORD comment_len = f.dwHeaderSizes[1];
    assert(comment_len > 0);
    assert(comment_len <= 255);
    
    const DWORD setup_len = f.dwHeaderSizes[2];
    assert(setup_len > 0);
    
    const DWORD hdr_len = ident_len + comment_len + setup_len;
    assert(cb == (sizeof(F) + hdr_len));

    const BYTE* const pb = static_cast<const BYTE*>(pv);    
    const BYTE* const hdr_ptr = pb + sizeof(F);
    
    const ULONG len = 1 + 1 + 1 + hdr_len;
    //1 byte = number of headers - 1
    //1 byte = ident len
    //1 byte = comment len
    //(len of setup_len is implied by total len)
    
    EbmlIO::File& file = m_context.m_file;
    
    file.WriteID2(0x63A2);  //Codec Private
    file.Write4UInt(len);

    BYTE val = 2;  //number of headers - 1
    file.Write(&val, 1);
    
    val = static_cast<BYTE>(ident_len);
    file.Write(&val, 1);
    
    val = static_cast<BYTE>(comment_len);
    file.Write(&val, 1);
    
    file.Write(hdr_ptr, hdr_len);
}


HRESULT MkvStreamAudioVorbis::Receive(IMediaSample* pSample)
{
    assert(pSample);
    
    //TODO: verify this
    if (m_context.m_file.GetStream() == 0)
        return S_OK;  //TODO: send EOS immediately (and return S_FALSE)

    VorbisFrame* const pFrame = new (std::nothrow) VorbisFrame(pSample);
    assert(pFrame);  //TODO
    
    m_context.NotifyAudioFrame(this, pFrame);

    return S_OK;
}


int MkvStreamAudioVorbis::EndOfStream()
{
    return m_context.NotifyAudioEOS(this);
}


}  //end namespace MkvMux
